// Copyright (c) Microsoft Corporation.  All rights reserved.
#include "Common.h"

CContent::CContent(CONTENT_ID contentId)
{
    m_contentId = contentId;

    m_hData = NULL;
    m_cbData = 0;
    
    m_dwFlags = 0;
    
    // for SCF content
    m_pParsedContent = NULL;
    m_pContentView = NULL;
    
    // for SCF context menu
    m_pContextMenuView = NULL;
    
    // for image contents
    m_pBitmap = NULL;
}

CContent::~CContent()
{
     // clear all data
     _ClearParsedContentAndView();
     _ClearBitmap();
     _ClearRawData();
}

VOID CContent::_ClearRawData()
{
    if (m_hData)
    {
        GlobalFree(m_hData);
        m_hData = NULL;
        m_cbData = 0;
    }
}

VOID CContent::_ClearParsedContentAndView()
{
    // free parsed content and view
    m_dwFlags &= ~CONTENT_FLAGS_PARSED;
    SAFE_DELETE(m_pContentView);
    SAFE_DELETE(m_pContextMenuView);
    SAFE_DELETE(m_pParsedContent);
}

VOID CContent::_ClearBitmap()
{
    SAFE_DELETE(m_pBitmap);
}

HRESULT CContent::SetData(const unsigned char* pData, DWORD cbData)
{
    HRESULT hr = S_OK;
    HGLOBAL hData;
    
    if (!pData)
    {
        return E_POINTER;
    }
    
    //
    // Using GlobalAlloc instead of new, HeapAlloc...
    // for usefulness of CreateStreamOnHGlobal
    //
    
    hData = GlobalAlloc(GMEM_MOVEABLE, cbData);
    
    if (hData)
    {
        PVOID pDataDst = GlobalLock(hData);
        
        if (pDataDst)
        {
            memcpy(pDataDst, pData, cbData);
            GlobalUnlock(hData);

            // Do not free content view to keep view states (e.g., active menu item, active dialog button).
            // instead, clear the parsed flag.
            ///////// _ClearParsedContentAndView();
            m_dwFlags &= ~CONTENT_FLAGS_PARSED;
			
            _ClearBitmap();// free GDI+ bitmap and IStream
			_ClearRawData();
			
			m_hData = hData;
			m_cbData = cbData;
		}
		else
		{
			hr = HRESULT_FROM_WIN32(GetLastError());
			GlobalFree(hData);
		}
	}
	else
	{
		hr = E_OUTOFMEMORY;
	}
	
	return hr;
}

//
// Set SCF Tree to Content
// This is not for normal SideShow Content.
// This is for internal special content.
// e.g.) HomeScreen Context Menu
//       Select User Menu Content
//
HRESULT CContent::SetScfTree(CScfContentBase *pScfTree)
{
    if (!pScfTree)
    {
        return E_POINTER;
    }
    
    _ClearRawData();

    SAFE_DELETE(m_pParsedContent);
    m_pParsedContent = pScfTree;
    m_dwFlags |= CONTENT_FLAGS_PARSED;

    // notify view object that the data is updated.
    _NotifyUpdateDataToViewObject();

    return S_OK;
}

// content enter
VOID CContent::OnContentEnter()
{
    if (m_pContentView)
    {
        // initialize content view state
        m_pContentView->OnEnter();
    }
}

// context menu enter
VOID CContent::OnContextMenuEnter()
{
    if (m_pContextMenuView)
    {
        // initialize context menu view state
        m_pContextMenuView->OnEnter();
    }
}

// application exit
VOID CContent::OnApplicationExit()
{
    // clear rendering data
    _ClearParsedContentAndView();
    _ClearBitmap();
}

// parse SCF xml to CScfElement tree
HRESULT CContent::_ParseScf(CScfContentBase **ppParsedScf)
{
    HRESULT hr = S_OK;
    
    SAFE_DELETE(m_pParsedContent);

    if (m_hData == NULL || m_cbData == 0)
    {
        return E_FAIL;
    }

#if DBG
/////////////// debug
{
    WCHAR *pwszData = NULL;
    DWORD cchData;
    
    PVOID pData = GlobalLock(m_hData);
    
    UTF8ToWideChar((BYTE *)pData, m_cbData, &pwszData, &cchData);
    
    if (pwszData)
    {
        OutputDebugString(pwszData);
        OutputDebugString(L"\r\n");
        delete [] pwszData;
    }
    
    GlobalUnlock(m_hData);
}
/////////////////////
#endif

#if 1
{
    // workaround for SAX parse error when cbData is including terminate-null.
    BYTE *pData = (BYTE *)GlobalLock(m_hData);
    if (pData)
    {
        if (pData[m_cbData - 1] == '\0')
        {
            pData[m_cbData - 1] = ' ';
        }
        GlobalUnlock(m_hData);
    }
}
#endif

    //
    // Parse
    //
    hr = CScfParser::Parse(m_hData, ppParsedScf);

    m_dwFlags |= CONTENT_FLAGS_PARSED;

    return hr;
}

//
// notify view object that the data is updated.
//
VOID CContent::_NotifyUpdateDataToViewObject()
{
    if (m_pContentView)
    {
        if ((m_pContentView->GetViewType() == SCF_CONTENT_VIEW && m_pParsedContent->GetElementType() == SCF_CONTENT_ELEMENT) ||
            (m_pContentView->GetViewType() == SCF_DIALOG_VIEW  && m_pParsedContent->GetElementType() == SCF_DIALOG_ELEMENT) ||
            (m_pContentView->GetViewType() == SCF_MENU_VIEW    && m_pParsedContent->GetElementType() == SCF_MENU_ELEMENT))
        {
            m_pContentView->OnUpdateData(m_pParsedContent);
        }
        else
        {
            SAFE_DELETE(m_pContentView);
        }
    }

    if (m_pContextMenuView)
    {
        if (m_pParsedContent->GetElementType() == SCF_MENU_ELEMENT)
        {
            m_pContextMenuView->OnUpdateData(m_pParsedContent);
        }
        else
        {
            SAFE_DELETE(m_pContextMenuView);
        }
    }
}

//
// rendering <content>, <menu> or <dialog>
//
HRESULT CContent::RenderContent(HDC hdc)
{
    HRESULT hr = S_OK;

    if ((m_dwFlags & CONTENT_FLAGS_PARSED) == 0)
    {
        hr = _ParseScf(&m_pParsedContent);

		if (SUCCEEDED(hr))
		{
			// notify view object that the data is updated.
			_NotifyUpdateDataToViewObject();
		}
	}
	
	// create new view object if none exists
	if (SUCCEEDED(hr) && m_pParsedContent != NULL && m_pContentView == NULL)
	{
		switch(m_pParsedContent->GetElementType())
		{
			case SCF_CONTENT_ELEMENT:
				m_pContentView = new(std::nothrow) CContentView();
				break;
			case SCF_MENU_ELEMENT:
				m_pContentView = new(std::nothrow) CMenuView();
				break;
			case SCF_DIALOG_ELEMENT:
				m_pContentView = new(std::nothrow) CDialogView();
				break;
		}
		
		if (m_pContentView)
		{
			hr = m_pContentView->Initialize(m_pParsedContent);
			
			if (SUCCEEDED(hr))
			{
				hr = m_pContentView->OnSize(
						hdc,
						CDevice::GetDeviceHorizontalResolution(),
						CDevice::GetDeviceVerticalResolution() - CRenderer::GetTitlebarHeight());


                m_pContentView->OnEnter();
            }

            if (FAILED(hr))
            {
                SAFE_DELETE(m_pContentView);
            }
        }
        else
        {
            hr = E_OUTOFMEMORY;
        }
    }
    
    if (SUCCEEDED(hr) && m_pContentView != NULL)
    {
        if (!m_pContentView->IsContentLayoutValid())
        {
            hr = m_pContentView->OnSize(
                            hdc,
                            CDevice::GetDeviceHorizontalResolution(),
                            CDevice::GetDeviceVerticalResolution() - CRenderer::GetTitlebarHeight());
        }
    }
    
    if (SUCCEEDED(hr) && m_pContentView != NULL)
    {
        // draw title bar
        m_pContentView->OnNCPaint(hdc,
                            0, 0,
                            CDevice::GetDeviceHorizontalResolution(),
                            CRenderer::GetTitlebarHeight());
        
        // draw content
        m_pContentView->OnPaint(hdc,
                            0, CRenderer::GetTitlebarHeight(),
                            CDevice::GetDeviceHorizontalResolution(),
                            CDevice::GetDeviceVerticalResolution());
    }

    return hr;
}

//
// rendering context menu
//
HRESULT CContent::RenderContextMenu(HDC hdc)
{
    HRESULT hr = S_OK;

    if ((m_dwFlags & CONTENT_FLAGS_PARSED) == 0)
    {
        hr = _ParseScf(&m_pParsedContent);

		if (SUCCEEDED(hr))
		{
			// notify view object that the data is updated.
			_NotifyUpdateDataToViewObject();
		}
	}
	
	// create new view object if none exists
	if (SUCCEEDED(hr) && m_pParsedContent != NULL && m_pContextMenuView == NULL)
	{
		m_pContextMenuView = new(std::nothrow) CContextMenuView();

        if (m_pContextMenuView)
        {
            hr = m_pContextMenuView->Initialize(m_pParsedContent);
            
            if (SUCCEEDED(hr))
            {
                hr = m_pContextMenuView->OnSize(
                        hdc,
                        CDevice::GetDeviceHorizontalResolution(),
                        CDevice::GetDeviceVerticalResolution());
                
                m_pContextMenuView->OnEnter();
            }

            if (FAILED(hr))
            {
                SAFE_DELETE(m_pContextMenuView);
            }
        }
        else
        {
            hr = E_OUTOFMEMORY;
        }
    }

    if (SUCCEEDED(hr) && m_pContextMenuView != NULL)
    {
        if (!m_pContextMenuView->IsContentLayoutValid())
        {
            hr = m_pContextMenuView->OnSize(
                            hdc,
                            CDevice::GetDeviceHorizontalResolution(),
                            CDevice::GetDeviceVerticalResolution());
        }
    }

    if (SUCCEEDED(hr) && m_pContextMenuView != NULL)
    {
        // draw context menu
        m_pContextMenuView->OnPaint(hdc,
                            0, 0,
                            CDevice::GetDeviceHorizontalResolution(),
                            CDevice::GetDeviceVerticalResolution());
    }

    return hr;  
}

//
// S_OK    = Handled
// S_FALSE = Not Handled
// E_XXX   = Error
//
HRESULT CContent::OnDeviceEvent(CDevice::DeviceEvent DeviceEvent)
{
    HRESULT hr = S_FALSE;

    if (g_pDataManager->IsContextMenuActive())
    {
        //
        // special handling for context menu
        //
        if (DeviceEvent == CDevice::ButtonBack ||
            DeviceEvent == CDevice::ButtonMenu)
        {
            g_pDataManager->ClearContextMenu();

            hr = S_OK;
        }
        else
        {
            if (m_pContextMenuView)
            {
                hr = m_pContextMenuView->OnDeviceEvent(DeviceEvent);
            }
        }
    }
    else
    {
        if (m_pParsedContent &&
            (m_pParsedContent->GetElementType() == SCF_CONTENT_ELEMENT ||
            m_pParsedContent->GetElementType() == SCF_MENU_ELEMENT ||
            (m_pParsedContent->GetElementType() == SCF_DIALOG_ELEMENT && DeviceEvent == CDevice::ButtonBack)))
        {
            //
            // button handling for content and menu
            //
            if ((DeviceEvent == CDevice::ButtonSelect ||
                DeviceEvent == CDevice::ButtonPrevious ||
                DeviceEvent == CDevice::ButtonNext ||
                DeviceEvent == CDevice::ButtonBack) &&
                m_pParsedContent->GetButtonCount() != 0)
            {
                for(size_t i = 0; i < m_pParsedContent->GetButtonCount(); i++)
                {
                    CScfBtnElement *pBtn = m_pParsedContent->GetButton(i);
                    
                    if ((DeviceEvent == CDevice::ButtonSelect   && pBtn->m_btnKey == SCF_BUTTON_SELECT) ||
                        (DeviceEvent == CDevice::ButtonPrevious && pBtn->m_btnKey == SCF_BUTTON_LEFT) ||
                        (DeviceEvent == CDevice::ButtonNext     && pBtn->m_btnKey == SCF_BUTTON_RIGHT) ||
                        (DeviceEvent == CDevice::ButtonBack     && pBtn->m_btnKey == SCF_BUTTON_BACK))
                    {
                        CNodeApplication *pAppNode = g_pDataManager->GetCurrentApplication();
                        
                        g_pDataManager->GetDDI()->SendNavigationEvent(
                            pAppNode->m_guidApplicationId,
                            g_pDataManager->GetCurrentContentId(),
                            pBtn->m_targetId,
                            pBtn->m_btnKey);
                        
                        g_pDataManager->SetCurrentContentId(pBtn->m_targetId);
                        
                        CContent *pContent = pAppNode->GetContent(pBtn->m_targetId);
                        if (pContent)
                        {
                            pContent->OnContentEnter();
                        }

                        hr = S_OK;
                        break;
                    }
                }
            }
        }
        
        if (hr == S_FALSE)
        {
            //
            // delegating the device event to content
            //
            if (m_pContentView)
            {
                hr = m_pContentView->OnDeviceEvent(DeviceEvent);
            }
        }
    }
    
    return hr;
}

//
// this is called for image content
//
HRESULT CContent::DrawImage(HDC hdc, int x, int y, int cxDest, int cyDest)
{
    HRESULT hr = S_OK;
    
    if (!m_pBitmap)
    {
        hr = _CreateBitmap();
    }
    
    if (SUCCEEDED(hr) && m_pBitmap)
    {
#ifdef _CONTENT_USE_ATLIMAGE
        m_pBitmap->Draw(hdc, x, y, cxDest, cyDest);
#else
        Gdiplus::Graphics gr(hdc);
        gr.DrawImage(m_pBitmap, x, y, cxDest, cyDest);
#endif
    }
    
    return hr;
}

int CContent::GetImageWidth()
{
    HRESULT hr = S_OK;
    int w = 0;
    
    if (!m_pBitmap)
    {
        hr = _CreateBitmap();
    }
    
    if (SUCCEEDED(hr) && m_pBitmap)
    {
        w = m_pBitmap->GetWidth();
    }
    
    return w;
}

int CContent::GetImageHeight()
{
    HRESULT hr = S_OK;
    int h = 0;
    
    if (!m_pBitmap)
    {
        hr = _CreateBitmap();
    }
    
    if (SUCCEEDED(hr) && m_pBitmap)
    {
        h = m_pBitmap->GetHeight();
    }
    
    return h;
}

//
// private method to create GDI+ Bitmap object from content
// this is called for image content
//
HRESULT CContent::_CreateBitmap()
{
    HRESULT hr = S_OK;
    IStream *pStream = NULL;
    
    if (m_pBitmap)
    {
        return S_OK;
    }
    
    hr = CreateStreamOnHGlobal(m_hData, FALSE, &pStream);
    
    if (SUCCEEDED(hr))
    {
#ifdef _CONTENT_USE_ATLIMAGE
        m_pBitmap = new(std::nothrow) CImage();
        if (m_pBitmap)
        {
            hr = m_pBitmap->Load(pStream);
            
            if (FAILED(hr))
            {
                delete m_pBitmap;
                m_pBitmap = NULL;
            }
        }
        else
        {
            hr = E_OUTOFMEMORY;
        }
#else
        m_pBitmap = Gdiplus::Bitmap::FromStream(pStream, FALSE);
        if (!m_pBitmap)
        {
            hr = E_FAIL;
        }
#endif
    }
    
    SAFE_RELEASE(pStream);
    
    return hr;
}

//
// Check if this content has reference to the given imgid.
//
BOOL CContent::HasImageReferenceOf(CONTENT_ID imgid)
{
    BOOL bRet = FALSE;

    if (m_pParsedContent)
    {
        bRet = m_pParsedContent->HasImageReferenceOf(imgid);
    }

    return bRet;
}

//
// Invalidate View's Layout
// This is called when image content that this content refers to is updated.
// This will force a call to this view's OnSize() when this view's OnPaint is called.
//
VOID CContent::InvalidateView()
{
    if (m_pContentView)
    {
        m_pContentView->InvalidateContentLayout();
    }
    
    if (m_pContextMenuView)
    {
        m_pContextMenuView->InvalidateContentLayout();
    }
}

